#include "model.h"

#include <fstream>
#include <gl/glew.h>
#define GLFW_DLL
#include <gl/glfw.h>
#include <iostream>
#include <stdlib.h>
#include <string>

struct Vert{
    Vert(){ x=y=z=0; }
    Vert(float x, float y, float z):x(x),y(y),z(z){}
    float x, y, z;
};

struct Face{
    int ia, ib, ic; //indices
    int ina, inb, inc; // norm indices;
    int uva, uvb, uvc;

    void SetIndices(int a, int b, int c){ ia=a; ib=b; ic=c; }
    void SetNormIndices(int a, int b, int c){ ina=a; inb=b; inc=c; }
    void SetUVIndices(int a, int b, int c){ uva=a, uvb=b; uvc=c; }
};

Model::Model(std::string path){
    std::vector<float> vertices;
    std::vector<float> normals;
    std::vector<float> uvs;

    float a,b,c;
    char ctemp;
    std::ifstream file;
    std::string line;
    std::vector<int> indices;
    std::vector<int> uvindices;

    std::vector<Vert> verts;
    std::vector<Vert> norms;
    std::vector<Vert> rawuvs;
    std::vector<Face> faces;

    std::string textureLocation = path.substr(0, path.find('.'))+std::string(".tga");

    if(path.find(".obj") == std::string::npos){
        path.append(".obj");
    }

    numTriangles = 0;

    file.open(path.c_str());
    if(file.is_open()==false){
        std::cout<<"Unable to open model: '"<<path<<"'"<<std::endl;
        ///TODO make it load an error sign like Oblivion
        file.open("data\\simplehouse.obj");
        return;
    }

    while(file.good()){
        getline(file, line);

        if(line[0]=='v' and line[1]==' '){
            sscanf (line.c_str(), "%c %f %f %f", &ctemp, &a, &b, &c);
            verts.push_back(Vert(a, -c, b));
        }
        else if(line[0]=='v' and line[1]=='n'){
            sscanf (line.c_str(), "%c%c %f %f %f", &ctemp, &ctemp, &a, &b, &c);
            norms.push_back(Vert(a, -c, b));
        }
        else if(line[0]=='v' and line[1]=='t'){
            sscanf (line.c_str(), "%c%c %f %f", &ctemp, &ctemp, &a, &b);
            rawuvs.push_back(Vert(a, b, 0));
        }

        else if(line[0]=='f' and line[1]==' '){// first 0,3,6 are vertexIndices, 1,4,7 are texture, 2,5,8 are vertnorm, k is the variable
            int iverts[3];
            int begin = 0;
            int len = 0;
            Face face;

            for(unsigned int i=0;i<3;i++){
                begin = line.find(' ', begin)+1;
                len = line.find('/', begin)-begin;
                iverts[i] = atoi(line.substr(begin, len).c_str()) - 1;
            }
            face.SetIndices(iverts[0], iverts[1], iverts[2]);

            begin = 0;
            len = 0;
            for(unsigned int i=0;i<3;i++){
                begin = line.find(' ', begin)+1;
                begin = line.find('/', begin)+1;
                len = line.find('/', begin)-begin;
                if(len > 0){
                    iverts[i] = atoi(line.substr(begin, len).c_str()) - 1;
                }
                else{
                    iverts[i] = -1;
                }
            }
            face.SetUVIndices(iverts[0], iverts[1], iverts[2]);

            begin = 0;
            len = 0;
            for(unsigned int i=0;i<3;i++){
                begin = line.find('/', begin)+1;
                begin = line.find('/', begin)+1;
                len = line.find(' ', begin)-begin;
                iverts[i] = atoi(line.substr(begin, len).c_str()) - 1;
            }
            face.SetNormIndices(iverts[0], iverts[1], iverts[2]);

            faces.push_back(face);
        }
    }
    file.close();

    for(auto face:faces){
        vertices.push_back(verts.at(face.ia).x);
        vertices.push_back(verts.at(face.ia).y);
        vertices.push_back(verts.at(face.ia).z);
        vertices.push_back(1);
        vertices.push_back(verts.at(face.ib).x);
        vertices.push_back(verts.at(face.ib).y);
        vertices.push_back(verts.at(face.ib).z);
        vertices.push_back(1);
        vertices.push_back(verts.at(face.ic).x);
        vertices.push_back(verts.at(face.ic).y);
        vertices.push_back(verts.at(face.ic).z);
        vertices.push_back(1);
    }
    for(auto face:faces){
        normals.push_back(norms.at(face.ina).x);
        normals.push_back(norms.at(face.ina).y);
        normals.push_back(norms.at(face.ina).z);
        normals.push_back(1);
        normals.push_back(norms.at(face.inb).x);
        normals.push_back(norms.at(face.inb).y);
        normals.push_back(norms.at(face.inb).z);
        normals.push_back(1);
        normals.push_back(norms.at(face.inc).x);
        normals.push_back(norms.at(face.inc).y);
        normals.push_back(norms.at(face.inc).z);
        normals.push_back(1);
    }
    if(faces.at(0).uva!=-1){
        for(auto face:faces){
            uvs.push_back(rawuvs.at(face.uva).x);
            uvs.push_back(rawuvs.at(face.uva).y);

            uvs.push_back(rawuvs.at(face.uvb).x);
            uvs.push_back(rawuvs.at(face.uvb).y);

            uvs.push_back(rawuvs.at(face.uvc).x);
            uvs.push_back(rawuvs.at(face.uvc).y);
        }
    }

    glGenSamplers(1, &sampler);
    glSamplerParameteri(sampler , GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(sampler , GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(sampler , GL_TEXTURE_MIN_FILTER , GL_LINEAR_MIPMAP_LINEAR);
    glSamplerParameteri(sampler , GL_TEXTURE_MAG_FILTER , GL_LINEAR);

    glGenVertexArrays(1, &drawVAO);
    glBindVertexArray(drawVAO);

    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glfwLoadTexture2D(textureLocation.c_str(), GLFW_BUILD_MIPMAPS_BIT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    glGenBuffers(1, &verticesVBO);
    glBindBuffer(GL_ARRAY_BUFFER, verticesVBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(float), &vertices[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &normalsVBO);
    glBindBuffer(GL_ARRAY_BUFFER, normalsVBO);
    glBufferData(GL_ARRAY_BUFFER, normals.size()*sizeof(float), &normals[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &uvsVBO);
    glBindBuffer(GL_ARRAY_BUFFER, uvsVBO);
    glBufferData(GL_ARRAY_BUFFER, uvs.size()*sizeof(float), &uvs[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);

    glGenVertexArrays(1, &pick_vao);
    glBindVertexArray(pick_vao);

    glBindBuffer(GL_ARRAY_BUFFER, verticesVBO);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);

    numTriangles = faces.size();
}

void Model::Draw(){
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glBindVertexArray(drawVAO);
    glDrawArrays(GL_TRIANGLES, 0, numTriangles*3);
    glBindVertexArray(0);
}

void Model::Pick(){
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glBindVertexArray(pick_vao);
    glDrawArrays(GL_TRIANGLES, 0, numTriangles*3);
    glBindVertexArray(0);
}
